/*
 * gameServer.c
 *
 * This is the server for Retro-Warfare
 *
 * This will house all the logic required to run Retro-Warfare
 *
 *  Created on: Dec 15, 2011
 *      Author: Jonathan Marokhovsky
 */

#include "gameServer.h"
#include "gameRules.h"
#include "msock.h"

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>

#define DEBUG		1

#define MAP_SIZE	((TOTAL_HEIGHT * (MAP_WIDTH + 1)) + 1)

int playerCount;
int maxPlayers = MAX_PLAYERS;
Player playerList[MAX_PLAYERS];
int playerSockets[MAX_PLAYERS];
Bullet bulletList[MAX_BULLETS];
int bulletCount;
char theMap[MAP_SIZE];

int main()
{
	int maxRecvFirstMess = MAX_NAME_LEN + IP_LEN + 1 + 5 + 3; // max name + ip length + 1 char (avatar) + 5 (max port length) + delim count

	playerCount = 0;
	bulletCount = 0;
	int bytes;
	char tempAv;
	char *tempName, *tempIP, *tempStrChar;
	char theTempName[MAX_NAME_LEN + 1], theTempIP[IP_LEN + 1];
	tempName = theTempName;
	tempIP = theTempIP;
	char recvdMess[maxRecvFirstMess];
	char cmd;
	int tempPort;
	int tempSock;
	int servSock;
	fd_set readSet;
	struct itimerval sendInterval;
	struct timeval timeout;

	/* Initialize the map */
	strncpy(theMap, theOriginalMap, MAP_SIZE);
	printf("Starting the server...\n");
	printf("The map will be:\n");
	printf("%s\n", theMap);

	/* Set up the time interval for sending out messages */
	sendInterval.it_value.tv_sec = 0;
	sendInterval.it_value.tv_usec = UPDATE_RATE;
	sendInterval.it_interval.tv_sec = 0;
	sendInterval.it_interval.tv_usec = UPDATE_RATE;
	/* Set up timeout interval */
	timeout.tv_sec = 0;
	timeout.tv_usec = 250000; // block for about a quarter of a second

	signal(SIGALRM, updateMap);

	/* Set up the socket */
	if ((servSock = msockcreate(RECV, SERV_IP, SERV_PORT)) < 0)
	{
		perror("ERROR: creating server socket");
		exit(EXIT_FAILURE);
	}

	/* Set the player's socket to nonblocking */
	if (fcntl(servSock, F_SETFL, O_NONBLOCK) < 0)
	{
		perror("ERROR: set nonblocking");
		exit(EXIT_FAILURE);
	}
	while (1)
	{
		bytes = 0; // reset it for every loop
		FD_ZERO(&readSet);
		if (FD_SET(servSock, &readSet) < 0)
		{
			perror("ERROR: adding server socket to set");
		}
		/* Wait for a new player */
		bytes = select(servSock + 1, &readSet, NULL, NULL, &timeout);
		if (bytes == 0 && playerCount == 0)
		{
			// do nothing
		}
		else if (bytes < 0 && errno != EINTR)
		{
			perror("ERROR: select");
			exit(EXIT_FAILURE);
		}
		else if (bytes == 0)
		{

		}
		else
		{
			if (FD_ISSET(servSock, &readSet))
			{
				if (playerCount == 0)
				{
					// This is the first player connecting, start the timer
					setitimer(ITIMER_REAL, &sendInterval, NULL);
				}
				if (mrecv(servSock, recvdMess, maxRecvFirstMess) < 0)
				{
					perror("ERROR: receiving message");
					exit(EXIT_FAILURE);
				}
				tempName = strtok(recvdMess, DELIM);
				if (strlen(tempName) > 1)
				{
					/* It is definitely a player */
					tempStrChar = strtok(NULL, DELIM);
					tempAv = *tempStrChar;
					if (findPlayer(tempAv) < 0) // check that the player doesn't already exist
					{
						tempIP = strtok(NULL, DELIM);
						tempPort = atoi(strtok(NULL, DELIM));
						/* New player accepted, log it */
						if (playerCount < maxPlayers)
						{
							sleep(1); // race condition?
							if ((tempSock = newPlayerSocket(tempIP, tempPort)) < 0)
							{
								perror("ERROR: new player socket creation");
								exit(EXIT_FAILURE);
							}
							playerSockets[playerCount] = tempSock;
							newPlayer(tempAv, tempName, tempIP, tempPort, playerList);
						}
						else
						{
							// Don't do anything, the player's client will die because of the lack of response
						}
					}
				}
				else
				{
					// This is either an action, an exit message, or a very short name
					if (strcmp(tempName, ACTION_FLAG) == 0)
					{
						tempStrChar = strtok(NULL, DELIM);
						tempAv = *tempStrChar;
						tempStrChar = strtok(NULL, DELIM);
						cmd = *tempStrChar;
						attemptAction(tempAv, cmd);
					}
					else if (strcmp(tempName, EXIT_FLAG) == 0)
					{
						tempStrChar = strtok(NULL, DELIM);
						tempAv = *tempStrChar;
						removePlayer(tempAv);
					}
					else
					{
						/* It is definitely a player */
						tempStrChar = strtok(NULL, DELIM);
						tempAv = *tempStrChar;
						if (findPlayer(tempAv) < 0) // check that the player doesn't already exist
						{
							tempIP = strtok(NULL, DELIM);
							tempPort = atoi(strtok(NULL, DELIM));
							/* New player accepted, log it */
							if (playerCount < maxPlayers)
							{
								if ((tempSock = newPlayerSocket(tempIP, tempPort)) < 0)
								{
									perror("ERROR: new player socket creation");
									exit(EXIT_FAILURE);
								}
								playerSockets[playerCount] = tempSock;
								newPlayer(tempAv, tempName, tempIP, tempPort, playerList);
							}
							else
							{
								// Don't do anything, the player's client will die because of the lack of response
							}
						}

					}
				}
			}
			else
			{

			}
		}
	}
	return 0;
}

/*
 * This creates a new player with the given information.
 */
int newPlayer(char av, char *name, char *ip, int port, Player playerArrayPt[])
{
	int ret = 0;
	Player tempPlayer;
	Point initPos;

	/* Avatar */
	tempPlayer.avatar = av;
	/* Name */
	tempPlayer.name = name;
	/* IP and port */
	tempPlayer.ip = ip;
	tempPlayer.port = port;

	/* Score */
	tempPlayer.score[0] = 0;
	tempPlayer.score[1] = 0;
	/* Randomize the initial position */
	initPos = initializePosition(av);
	tempPlayer.position = initPos;
	playerArrayPt[playerCount] = tempPlayer;
	addPlayerToScoreBoard (tempPlayer, playerCount);
	playerCount++;
	return ret;
}

Point initializePosition(char av)
{
	int randFlag = 0;
	int randX, randY, coord;
	Point initPos;
	while (randFlag == 0)
	{
		randX = (rand() % (MAP_WIDTH - 2)) + 1; // Random number between 1 and 1-board_width
		randY = (rand() % (MAP_HEIGHT - 2)) + 1 + INSTRUCT_HEIGHT; // add the instruction height so that's not run into
		initPos.x = randX;
		initPos.y = randY;
		coord = convertPoint(initPos);
		if (theMap[coord] == ' ')
		{
			randFlag++;
			theMap[coord] = av;
			break;
		}
	}
	return initPos;
}
int addPlayerToScoreBoard(Player player, int playerCount)
{
	int ret = 0;
	int i;
	int arrLoc; // the location in the map array where the player info will be put
	int maxName = MAX_NAME_LEN;
	int halfMap = MAP_WIDTH / 2;
	int finalLen = halfMap - 5; // the length of the string which will contain all the player info (last 5 spaces are always blank)
	char playerInfo[finalLen]; // This is where the string will be stored
	char avOut[3]; // will always be 3 because valid avatars are single characters
	char score[5]; // will always be 5 because the max score is in single digits
	char *pInfoPt = playerInfo; // use this pointer so that we can jump to different points in the string
	Point loc; // an (x, y) style coordinate of where the string will go

	sprintf(avOut, "(%c)", player.avatar); // prepare the avatar for output
	sprintf(score, "[%d:%d]", player.score[0], player.score[1]); // prepare the score for output

	/* fill playerInfo with underscores for now */
	for (i = 0; i < finalLen; i++)
	{
		playerInfo[i] = '_';
	}
	strncpy(pInfoPt, player.name, strlen(player.name)); // make sure this does not include the null at the end
	strncpy((pInfoPt + maxName), avOut, strlen(avOut)); // start it at the end of the max length of a name
	strncpy((pInfoPt + maxName + strlen(avOut)), score, strlen(score) + 1);

	/* move the screen pointer to the next available spot */
	loc.y = TOTAL_HEIGHT - ((playerCount - 1) / 2); // since this rounds down, 0 and 1 are 0, 1 and 2 are 1, ..., 9 and 10 are 5.
	if (playerCount % 2)
	{
		loc.x = halfMap;
	}
	else
	{
		loc.x = 0;
	}
	arrLoc = convertPoint(loc);

	/* Fill the map with the gathered player info */
	for (i = 0; i < finalLen; i++)
	{
		theMap[i + arrLoc] = playerInfo[i];
	}
	return ret;
}

int removePlayer(char av)
{
	int playerIndex;
	int playerSock;
	Player tempPlayer;
	if ((playerIndex = findPlayer(av)) >= 0)
	{
//		playerList[playerIndex] = (Player) NULL; // don't do anything with this, it will get overwritten
		playerSock = playerSockets[playerIndex];
		close(playerSock);
//		playerSockets[playerIndex] = NULL; // don't do anything with this, it will get overwritten
		while (playerIndex < (playerCount - 1))
		{
			playerIndex++;
			playerSockets[playerIndex - 1] = playerSockets[playerIndex];
			playerList[playerIndex - 1] = playerList[playerIndex];
		}
		playerCount--;
	}
	return 0;
}

int findPlayer(char av)
{
	int ret;
	for (ret = 0; ret < playerCount; ret++)
	{
		if (playerList[ret].avatar == av)
		{
			return ret;
		}
	}
	ret = -1;
	return ret;
}

void updateMap(int sig)
{
	int i;
	struct sockaddr_in tempAddr;
	for (i = 0; i < playerCount; i++)
	{
		Player tempPlayer = playerList[i];
		tempAddr.sin_family = AF_INET;
		tempAddr.sin_port = htons((short) tempPlayer.port);
		tempAddr.sin_addr.s_addr = inet_addr(tempPlayer.ip);
		if (sendto(playerSockets[i], theMap, strlen(theMap) + 1, 0, (struct sockaddr *) &tempAddr, sizeof(tempAddr)) < 0)
		{
			perror("ERROR: sending map");
			exit(1);
		}
	}
	signal(SIGALRM, updateMap);
}

void attemptAction(char av, char cmd)
{
	int index;
	Player player;
	Point temp;
	if ((index = findPlayer(av)) >= 0)
	{
		player = playerList[index];
		temp = player.position;
		switch (cmd)
		{
			case 'w':
			case 'W':
				temp.y++;
				movePlayer(av, player.position, temp);
				break;
			case 'a':
			case 'A':
				temp.x--;
				movePlayer(av, player.position, temp);
				break;
			case 's':
			case 'S':
				temp.y--;
				movePlayer(av, player.position, temp);
				break;
			case 'd':
			case 'D':
				temp.x++;
				movePlayer(av, player.position, temp);
				break;
			case 'i':
			case 'I':
				shotFired(index, NORTH, temp);
				break;
			case 'j':
			case 'J':
				shotFired(index, WEST, temp);
				break;
			case 'k':
			case 'K':
				shotFired(index, SOUTH, temp);
				break;
			case 'l':
			case 'L':
				shotFired(index, EAST, temp);
				break;
			default:
				break;
		}
	}
}

void movePlayer (char av, Point from, Point to)
{
	int tempTo;
	/* check that it's within the bounds of the board */
	if (to.x > 1 && to.x < MAP_WIDTH
				&& to.y > 1 + INSTRUCT_HEIGHT
				&& to.y < MAP_HEIGHT + INSTRUCT_HEIGHT)
	{
		tempTo = convertPoint(to);
		if (theMap[tempTo] == ' ') // check that the space is empty
		{
			theMap[convertPoint(from)] = ' ';
			theMap[tempTo] = av;
		}
	}
	/* otherwise do nothing */
}

void shotFired(int player, Direction dir, Point playerPos)
{
	int hitFlag = 0;
	Bullet ret;
	Point pos = playerPos;
	char temp;
	if (bulletCount < MAX_BULLETS)
	{

		ret.dir = dir;
		ret.pID = player;
		switch (dir)
		{
			/* NOTE: Player Position should be (x,y) */
			case NORTH:
				pos.y++;
				if (pos.y >= MAP_HEIGHT + INSTRUCT_HEIGHT)
				{
					hitFlag++;
				}
				else
				{
					temp = theMap[convertPoint(pos)];
					if ((temp == '-') || (temp == '|'))
					{
						hitFlag++;
					}
					else
					{
						//it's valid either way
						if (temp == ' ')
						{
							// do nothing in this case
						}
						else
						{
							hitFlag++;
							checkHit(temp, player);
						}
					}
				}
				break;
			case EAST:
				pos.x++;
				if (pos.x >= MAP_WIDTH)
				{
					hitFlag++;
				}
				else
				{
					temp = theMap[convertPoint(pos)];
					if ((temp == '-') || (temp == '|'))
					{
						hitFlag++;
					}
					else
					{
						//it's valid either way
						if (temp == ' ')
						{
							// do nothing in this case
						}
						else
						{
							hitFlag++;
							checkHit(temp, player);
						}
					}
				}
				break;
			case SOUTH:
				pos.y--;
				if (pos.y < 1 + INSTRUCT_HEIGHT)
				{
					hitFlag++;
				}
				else
				{
					temp = theMap[convertPoint(pos)];
					if ((temp == '-') || (temp == '|')
									|| (temp == '.'))
					{
						hitFlag++;
					}
					else
					{
						//it's valid either way
						if (temp == ' ')
						{
							// do nothing in this case
						}
						else
						{
							hitFlag++;
							checkHit(temp, player);
						}
					}
				}
				break;
			case WEST:
				pos.x--;
				if (pos.x < 1)
				{
					hitFlag++;
				}
				else
				{
					temp = theMap[convertPoint(pos)];
					if ((temp == '-') || (temp == '|'))
					{
						hitFlag++;
					}
					else
					{
						//it's valid either way
						if (temp == ' ')
						{
							// do nothing in this case
						}
						else
						{
							hitFlag++;
							checkHit(temp, player);
						}
					}
				}
				break;
			default:
				perror("ERROR: bad enum");
				exit(-1);
				break;
		}
		if (hitFlag == 0)
		{
			ret.position = pos;
			bulletList[bulletCount] = ret;
			bulletCount++;
		}
	}
}

void checkHit(char av, int playerIndex)
{
	int opponentIndex;
	if ((opponentIndex = findPlayer(av)) >= 0)
	{
		playerList[opponentIndex].score[1]++;
		playerList[playerIndex].score[0]++;
		checkWin(playerList[playerIndex]);
		checkLose(playerList[opponentIndex]);
	}
}

void checkWin(Player player)
{
	if (player.score[0] >= MAX_SCORE)
	{
		winAndRestart(player);
	}
}

void checkLose(Player player)
{
	if (player.score[1] >= MAX_DEATHS)
	{
		loseAndRestart(player);
	}
}

void winAndRestart(Player winner)
{
	char message[MAP_WIDTH];
	char *mapPt = theMap;
	Point tempPoint;
	int messLen;
	sprintf(message, "Congrats to %s(%c) who just won!!", winner.name, winner.avatar);
	messLen = strlen(message);
	tempPoint.x = messLen - ((MAP_WIDTH - messLen)/2);
	tempPoint.y = MAP_HEIGHT / 2;
	strncpy(*(mapPt + convertPoint(tempPoint)), message, messLen);
	restartGame();
}

void loseAndRestart(Player loser)
{
	char message[MAP_WIDTH];
	char *mapPt = theMap;
	Point tempPoint;
	int messLen;
	sprintf(message, "Wow, %s(%c) managed to die 99 times!!", loser.name, loser.avatar);
	messLen = strlen(message);
	tempPoint.x = messLen - ((MAP_WIDTH - messLen)/2);
	tempPoint.y = MAP_HEIGHT / 2;
	strncpy(*(mapPt + convertPoint(tempPoint)), message, messLen);
	restartGame();
}

void restartGame()
{
	int i;
	char mess[MAP_WIDTH];
	char *mapPt = theMap;
	Point tempPoint;
	int messLen;
	sprintf(mess, "Restarting in :");
	messLen = strlen(mess);
	tempPoint.x = messLen - ((MAP_WIDTH - messLen)/2);
	tempPoint.y = (MAP_HEIGHT / 2) + 1;
	strncpy(*(mapPt + convertPoint(tempPoint)), mess, messLen);
	tempPoint.x = 4 - ((MAP_WIDTH - 4)/2);
	tempPoint.y -= 2;
	strncpy(*(mapPt + convertPoint(tempPoint)), "3...", 4);
	sleep(1);
	strncpy(*(mapPt + convertPoint(tempPoint)), "2...", 4);
	sleep(1);
	strncpy(*(mapPt + convertPoint(tempPoint)), "1...", 4);
	sleep(1);

	/* start restarting */
	strcpy(theMap, theOriginalMap); // reset the map
	for (i = 0; i < playerCount; i++)
	{
		playerList[i].position = initializePosition(playerList[i].avatar);
	} // re-randomize the player positions
	bulletCount = 0; // remove all bullets
}

int newPlayerSocket(char ip[], int port)
{
	int sock;
	struct sockaddr_in addr;
	if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		perror("ERROR: setting up the player socket");
		exit(1);
	}

	memset((char *) &addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	if (inet_aton(ip, &addr.sin_addr) == 0)
	{
		fprintf(stderr, "inet_aton failed\n");
		exit(1);
	}
	return sock;
}
